# 一行代码统一规范 包管理器

这一次的知识点将涉及到

  1. npm钩子
  2. 统一包管理器的原理
  3. only-allow 源码

简单的来讲,就是借助 npm 提供的 preinstall 钩子中,执行一个对包管理器的判断,这里是用的 only-allow

# 前提

这里看一下 Vue3 源码 中 package.json

// vue-next/package.json
{
  "private": true,
  "version": "3.2.22",
  "scripts": {
    "preinstall": "node ./scripts/preinstall.js",
  }
}

npm 对 install 的生命周期提供了三个钩子:

  1. preinstall
  2. install
  3. postinstall

其他的 npm 钩子,参见 文档 (opens new window)

# ./scripts/preinstall.js

这里看一下 vue3中的 preinstall 代码 (opens new window)

if (!/pnpm/.test(process.env.npm_execpath || '')) {
  console.warn(
    `\u001b[33mThis repository requires using pnpm as the package manager ` +
      ` for scripts to work properly.\u001b[39m\n`
  )
  process.exit(1)
}

这里对包管理器的判断,是由node提供的环境变量实现的。

# 环境准备

# 克隆官方仓库
git clone https://github.com/pnpm/only-allow.git

这个包实现的功能和用法都在 README (opens new window) 文档上

简单的用法:

{
  "scripts": {
    "preinstall": "npx only-allow yarn"
  }
}

为了方便,借由 https://github1s.com 来阅读代码

# 源码

根据 package.json 中的 bin 和 main 中,主要代码是 bin.js (opens new window) :

#!/usr/bin/env node
const whichPMRuns = require('which-pm-runs')
const boxen = require('boxen')

// 参数的获取
const argv = process.argv.slice(2)
// 参数的判断
if (argv.length === 0) {
  console.log('Please specify the wanted package manager: only-allow <npm|pnpm|yarn>')
  process.exit(1)
}
const wantedPM = argv[0]
if (wantedPM !== 'npm' && wantedPM !== 'pnpm' && wantedPM !== 'yarn') {
  console.log(`"${wantedPM}" is not a valid package manager. Available package managers are: npm, pnpm, or yarn.`)
  process.exit(1)
}
// 获取使用的是哪一个 包管理器
const usedPM = whichPMRuns()
// 如果 得到使用的包,且使用包与参数不一样,则提示并退出进程
if (usedPM && usedPM.name !== wantedPM) {
  const boxenOpts = { borderColor: 'red', borderStyle: 'double', padding: 1 }
  switch (wantedPM) {
    case 'npm':
      console.log(boxen('Use "npm install" for installation in this project', boxenOpts))
      break
    case 'pnpm':
      console.log(boxen(`Use "pnpm install" for installation in this project.

If you don't have pnpm, install it via "npm i -g pnpm".
For more details, go to https://pnpm.js.org/`, boxenOpts))
      break
    case 'yarn':
      console.log(boxen(`Use "yarn" for installation in this project.

If you don't have Yarn, install it via "npm i -g yarn".
For more details, go to https://yarnpkg.com/`, boxenOpts))
      break
  }
  process.exit(1)
}

# 关于 which-pm-runs

module.exports = function () {
  if (!process.env.npm_config_user_agent) {
    return undefined
  }
  return pmFromUserAgent(process.env.npm_config_user_agent)
}

function pmFromUserAgent (userAgent) {
  const pmSpec = userAgent.split(' ')[0]
  const separatorPos = pmSpec.lastIndexOf('/')
  return {
    name: pmSpec.substr(0, separatorPos),
    version: pmSpec.substr(separatorPos + 1)
  }
}

也是通过 process 中env的环境变量来获取的 npm 包管理器

# 总结

总的来看,为了实现通过包管理器的需求,这里借助 preInstall 的钩子,在项目安装依赖的时候,对使用的npm包

# 参考资料

  1. 从 vue3 和 vite 源码中,我学到了一行代码统一规范团队包管理器的神器 若川 掘金 (opens new window)
  2. npm 钩子文档 (opens new window)